package io.itit.ecp.admin.provide.action.controller.miniprogram;


import cn.hutool.core.collection.CollectionUtil;
import icu.mhb.mybatisplus.plugln.extend.Joins;
import io.itit.ecp.admin.common.enums.IMeterDeviceEnums;
import io.itit.ecp.admin.common.enums.MeterDeviceStaticsTypeEnums;
import io.itit.ecp.admin.provide.action.resp.miniprogram.AiPredictionAccuracyResp;
import io.itit.ecp.admin.provide.action.resp.miniprogram.ElectricityPredictionRealityResp;
import io.itit.ecp.admin.provide.action.resp.miniprogram.PredictionRealityResp;
import io.itit.ecp.admin.server.entity.EnergyDataEntity;
import io.itit.ecp.admin.server.entity.EnergyForecastDataEntity;
import io.itit.ecp.admin.server.entity.SceneEdificeEntity;
import io.itit.ecp.admin.server.service.ISceneEdificeService;
import io.itit.grass.common.provide.action.resp.ResultResp;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * AI 预测
 */
@Api(tags = "AI预测")
@RestController
@RequestMapping("/mini/aiPrediction")
public class MiniAiPredictionController {

    @Resource
    private ISceneEdificeService sceneEdificeService;

    /**
     * 预测精度
     */
    @GetMapping("accuracy")
    @ApiOperation("预测精度")
    public ResultResp<List<AiPredictionAccuracyResp>> accuracy(@RequestParam(required = false) String sceneProjectId,
                                                               @RequestParam(required = false) String sceneEdificeId,
                                                               @RequestParam(required = false) String sceneCompanyId,
                                                               @RequestParam(required = false) String sceneFloorId,
                                                               @RequestParam(required = false) String startTime,
                                                               @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();
        List<EnergyForecastDataEntity> energyForecastDataList = getForecastDataList(sceneProjectId, sceneEdificeId, sceneCompanyId, sceneFloorId, start, end);
        LocalDateTime preStart = Objects.isNull(start) ? LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).minusYears(1) : start.minusYears(1);
        LocalDateTime preEnd = end.minusYears(1);
        List<EnergyForecastDataEntity> preEnergyForecastDataList = getForecastDataList(sceneProjectId, sceneEdificeId, sceneCompanyId, sceneFloorId, preStart, preEnd);
        List<AiPredictionAccuracyResp> list = new ArrayList<>();
        list.add(new AiPredictionAccuracyResp().setName("用电量")
                .setValue(energyForecastDataList.stream()
                        .filter(item -> StringUtils.pathEquals(item.getStaticsType(), MeterDeviceStaticsTypeEnums.POWER_CONSUMPTION.getValue()))
                        .map(EnergyForecastDataEntity::getForecastPrecision).findFirst().orElse(BigDecimal.ZERO))
                .setPreValue(preEnergyForecastDataList.stream()
                        .filter(item -> StringUtils.pathEquals(item.getStaticsType(), MeterDeviceStaticsTypeEnums.POWER_CONSUMPTION.getValue()))
                        .map(EnergyForecastDataEntity::getForecastPrecision).findFirst().orElse(BigDecimal.ZERO))
                .setMValue(BigDecimal.ZERO) //TODO:
                .setMRate(BigDecimal.ZERO));
        list.add(new AiPredictionAccuracyResp().setName("发电量")
                .setValue(energyForecastDataList.stream()
                        .filter(item -> StringUtils.pathEquals(item.getStaticsType(), MeterDeviceStaticsTypeEnums.POWER_GENERATION.getValue()))
                        .map(EnergyForecastDataEntity::getForecastPrecision).findFirst().orElse(BigDecimal.ZERO))
                .setPreValue(preEnergyForecastDataList.stream()
                        .filter(item -> StringUtils.pathEquals(item.getStaticsType(), MeterDeviceStaticsTypeEnums.POWER_GENERATION.getValue()))
                        .map(EnergyForecastDataEntity::getForecastPrecision).findFirst().orElse(BigDecimal.ZERO))
                .setMValue(BigDecimal.ZERO) // TODO: 同比值/同比率/环比值/环比率：待定
                .setMRate(BigDecimal.ZERO));
        return ResultResp.success(list);
    }

    private static List<EnergyForecastDataEntity> getForecastDataList(String sceneProjectId, String sceneEdificeId, String sceneCompanyId, String sceneFloorId, LocalDateTime start, LocalDateTime end) {
        List<EnergyForecastDataEntity> staticsTypeList = Joins.of(EnergyForecastDataEntity.class)
                .selectAs(builder -> builder.add("statics_type", "staticsType", false)
                        .add("SUM(forecast_precision)", "forecastPrecision", false))
                .eq(StringUtils.hasLength(sceneProjectId), EnergyForecastDataEntity::getSceneProjectId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyForecastDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneCompanyId), EnergyForecastDataEntity::getSceneCompanyId, sceneCompanyId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyForecastDataEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), EnergyForecastDataEntity::getSolidifyDate, start)
                .le(EnergyForecastDataEntity::getSolidifyDate, end)
                .groupBy(EnergyForecastDataEntity::getStaticsType)
                .joinList(EnergyForecastDataEntity.class);
        return staticsTypeList;
    }

    /**
     * 光伏发电预测与实际
     */
    @ApiOperation("光伏发电预测与实际")
    @GetMapping("powergeneration/predictionreality")
    public ResultResp<List<PredictionRealityResp>> powerGenerationPredictionReality(@RequestParam(required = false) String sceneProjectId,
                                                                                    @RequestParam(required = false) String sceneEdificeId,
                                                                                    @RequestParam(required = false) String sceneCompanyId,
                                                                                    @RequestParam(required = false) String sceneFloorId,
                                                                                    @RequestParam(required = false) String startTime,
                                                                                    @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();
        List<EnergyDataEntity> energyDataEntities = Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder.add("DATE_FORMAT(solidify_date,'%Y-%m-%d %H:%m')", "deviceCode", false)
                        .add("SUM(meter_value)", "meterValue", false))
                .eq(EnergyDataEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.readMeterType11.getValue())
                .eq(EnergyDataEntity::getStaticsType, MeterDeviceStaticsTypeEnums.POWER_GENERATION.getValue())
                .eq(StringUtils.hasLength(sceneProjectId), EnergyDataEntity::getSceneProjectId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneCompanyId), EnergyDataEntity::getSceneCompanyId, sceneCompanyId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyDataEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), EnergyDataEntity::getSolidifyDate, start)
                .le(EnergyDataEntity::getSolidifyDate, end)
                .groupBy(EnergyDataEntity::getSolidifyDate)
                .orderByAsc(EnergyDataEntity::getSolidifyDate)
                .joinList(EnergyDataEntity.class);
        Map<String, BigDecimal> timeValuesMap = energyDataEntities.stream()
                .collect(Collectors.toMap(EnergyDataEntity::getDeviceCode, EnergyDataEntity::getMeterValue));
        List<EnergyForecastDataEntity> forecastDataList = Joins.of(EnergyForecastDataEntity.class)
                .selectAs(builder -> builder.add("DATE_FORMAT(solidify_date,'%Y-%m-%d %H:%m')", "deviceCode", false)
                        .add("SUM(meter_forecast_value)", "meterForecastValue", false))
                .eq(StringUtils.hasLength(sceneProjectId), EnergyForecastDataEntity::getSceneProjectId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyForecastDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneCompanyId), EnergyForecastDataEntity::getSceneCompanyId, sceneCompanyId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyForecastDataEntity::getSceneFloorId, sceneFloorId)
                .eq(EnergyForecastDataEntity::getStaticsType, MeterDeviceStaticsTypeEnums.POWER_GENERATION.getValue())
                .ge(Objects.nonNull(start), EnergyForecastDataEntity::getSolidifyDate, start)
                .le(EnergyForecastDataEntity::getSolidifyDate, end)
                .groupBy(EnergyForecastDataEntity::getSolidifyDate)
                .joinList(EnergyForecastDataEntity.class);
        Map<String, BigDecimal> forecastDataTimeValueMap = forecastDataList.stream()
                .collect(Collectors.toMap(EnergyForecastDataEntity::getDeviceCode, EnergyForecastDataEntity::getMeterForecastValue));

        List<String> times = CollectionUtil.union(timeValuesMap.keySet(), forecastDataTimeValueMap.keySet()).stream().sorted().collect(Collectors.toList());

        List<PredictionRealityResp> list = new ArrayList<>();
        list.add(new PredictionRealityResp()
                .setName("实际")
                .setTime(times)
                .setValue(times.stream().map(item -> timeValuesMap.getOrDefault(item, BigDecimal.ZERO).setScale(2, RoundingMode.HALF_DOWN)).collect(Collectors.toList()))
        );

        list.add(new PredictionRealityResp()
                .setName("预测")
                .setType("line")
                .setTime(times)
                .setValue(times.stream().map(item -> forecastDataTimeValueMap.getOrDefault(item, BigDecimal.ZERO).setScale(2, RoundingMode.HALF_DOWN)).collect(Collectors.toList()))
        );
        return ResultResp.success(list);
    }


    /**
     * 用电负荷预测与实际
     */
    @ApiOperation("用电负荷预测与实际")
    @GetMapping("electricity/predictionreality")
    public ResultResp<List<ElectricityPredictionRealityResp>> electricityPredictionReality(@RequestParam(required = false) String sceneProjectId,
                                                                                           @RequestParam(required = false) String sceneEdificeId,
                                                                                           @RequestParam(required = false) String sceneCompanyId,
                                                                                           @RequestParam(required = false) String sceneFloorId,
                                                                                           @RequestParam(required = false) String startTime,
                                                                                           @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();

        List<EnergyDataEntity> energyDataEntities = Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder.add("scene_edifice_id", "sceneEdificeId", false)
                        .add("SUM(meter_value)", "meterValue", false))
                .eq(EnergyDataEntity::getStaticsType, MeterDeviceStaticsTypeEnums.POWER_CONSUMPTION.getValue())
                .eq(StringUtils.hasLength(sceneProjectId), EnergyDataEntity::getSceneProjectId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneCompanyId), EnergyDataEntity::getSceneCompanyId, sceneCompanyId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyDataEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), EnergyDataEntity::getSolidifyDate, start)
                .le(EnergyDataEntity::getSolidifyDate, end)
                .groupBy(EnergyDataEntity::getSolidifyDate)
                .orderByAsc(EnergyDataEntity::getSolidifyDate)
                .joinList(EnergyDataEntity.class);
        Map<String, BigDecimal> energyMeterValues = energyDataEntities.stream()
                .collect(Collectors.toMap(EnergyDataEntity::getSceneEdificeId, EnergyDataEntity::getMeterValue));

        List<EnergyForecastDataEntity> forecastDataList = Joins.of(EnergyForecastDataEntity.class)
                .selectAs(builder -> builder.add("scene_edifice_id", "sceneEdificeId", false)
                        .add("SUM(meter_forecast_value)", "meterForecastValue", false))
                .eq(StringUtils.hasLength(sceneProjectId), EnergyForecastDataEntity::getSceneProjectId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyForecastDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneCompanyId), EnergyForecastDataEntity::getSceneCompanyId, sceneCompanyId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyForecastDataEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), EnergyForecastDataEntity::getSolidifyDate, start)
                .le(EnergyForecastDataEntity::getSolidifyDate, end)
                .eq(EnergyForecastDataEntity::getStaticsType, MeterDeviceStaticsTypeEnums.POWER_CONSUMPTION.getValue())
                .groupBy(EnergyForecastDataEntity::getSolidifyDate)
                .joinList(EnergyForecastDataEntity.class);
        Map<String, BigDecimal> forecastDataTimeValueMap = forecastDataList.stream()
                .collect(Collectors.toMap(EnergyForecastDataEntity::getSceneEdificeId, EnergyForecastDataEntity::getMeterForecastValue));

        List<SceneEdificeEntity> edificeEntities = sceneEdificeService.lambdaQuery()
                .eq(SceneEdificeEntity::getSceneProjectId, sceneProjectId)
                .orderByAsc(SceneEdificeEntity::getEdificeName)
                .list();

        List<String> edificeNames = edificeEntities.stream().map(SceneEdificeEntity::getEdificeName).collect(Collectors.toList());
        List<ElectricityPredictionRealityResp> list = new ArrayList<>();
        list.add(new ElectricityPredictionRealityResp()
                .setName("实际")
                .setEdificeNames(edificeNames)
                .setValue(edificeEntities.stream().map(item -> energyMeterValues.getOrDefault(item.getId(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_DOWN)).collect(Collectors.toList()))
        );

        list.add(new ElectricityPredictionRealityResp()
                .setName("预测")
                .setType("line")
                .setEdificeNames(edificeNames)
                .setValue(edificeEntities.stream().map(item -> forecastDataTimeValueMap.getOrDefault(item.getId(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_DOWN)).collect(Collectors.toList()))
        );
        return ResultResp.success(list);
    }

}
